// @Author EthanScriptOn
// @Desc
package config_parser

import (
	"errors"
	"fmt"
	"gitee.com/fatzeng/srf_switch_basic_components/custom_exp"
	"gitee.com/fatzeng/srf_switch_basic_components/node"
	"gitee.com/fatzeng/srf_switch_basic_components/processor"
	"gitee.com/fatzeng/srf_switch_basic_components/proxy"
	"gitee.com/fatzeng/srf_switch_config/config_context"
	"gitee.com/fatzeng/srf_switch_config/config_define"
	"gitee.com/fatzeng/srf_switch_config/config_processor_management"
	"gitee.com/fatzeng/srf_switch_config/config_source_loader_management"
	"gitee.com/fatzeng/srf_switch_protocol/protocol/log"
)

type executedUnit struct {
	sourceType config_define.ConfigSourceType
	loaderData interface{}
	processor  processor.Processor
}

func StartParse(ctx *config_context.ConfigInitializationContext) (err error) {
	preStageResults, err := preparationStage()
	if err != nil {
		return
	}
	return implementationPhase(ctx, preStageResults)
}

func executedTabulation() ([]config_define.ConfigSourceType, error) {
	screenSupportedList, err := config_source_loader_management.GetSupportedList()
	if err != nil {
		return nil, err
	}
	if len(screenSupportedList) <= 0 {
		return nil, errors.New("can not found any supported list")
	}
	supportedTypes := make([]config_define.ConfigSourceType, 0)
	for sourceType := range screenSupportedList {
		supportedTypes = append(supportedTypes, sourceType)
	}
	return supportedTypes, nil
}

func executedSourceLoader(loaderManager config_source_loader_management.SourceLoaderManager, tabulation []config_define.ConfigSourceType) (map[config_define.ConfigSourceType]interface{}, error) {
	sourceResultMap := make(map[config_define.ConfigSourceType]interface{})
	for _, sourceType := range tabulation {
		hasLoader, err := loaderManager.HasSourceLoader(sourceType)
		if hasLoader && err == nil {
			loadResult, err := loaderManager.Load(sourceType)
			if err != nil {
				return nil, err
			}
			sourceResultMap[sourceType] = loadResult
		}
		switch err.(type) {
		case *custom_exp.NoResourceError:
			log.Logger().Warn("can not found any source loader for %s", sourceType)
			continue
		default:
			return nil, err
		}
	}
	if len(sourceResultMap) <= 0 {
		return nil, errors.New("can not obtain any source loader result")
	}
	return sourceResultMap, nil
}

func preLoadedResources(tabulation []config_define.ConfigSourceType) (map[config_define.ConfigSourceType]interface{}, error) {
	loaderManager, err := config_source_loader_management.GetMainSourceLoaderManager()
	if err != nil {
		return nil, err
	}
	return executedSourceLoader(loaderManager, tabulation)
}

func processorPreloading(tabulation []config_define.ConfigSourceType) (map[config_define.ConfigSourceType]processor.Processor, error) {
	return getUsedResourceProcessor(tabulation)
}

func preparationStage() ([]*executedUnit, error) {
	units := make([]*executedUnit, 0)
	tabulation, err := executedTabulation()
	if err != nil {
		return nil, err
	}
	sourceMap, err := preLoadedResources(tabulation)
	if err != nil {
		return nil, fmt.Errorf("preparation stage preLoadedResources error: %s", err)
	}
	processorMap, err := processorPreloading(tabulation)
	if err != nil {
		return nil, fmt.Errorf("preparation stage preLoadedResources error: %s", err)
	}
	for sourceType, processor := range processorMap {
		loaderData, ok := sourceMap[sourceType]
		if !ok {
			log.Logger().Warn("Unable to obtain the configuration loading result of s% type ", sourceType)
			continue
		}
		units = append(units, &executedUnit{
			sourceType: sourceType,
			loaderData: loaderData,
			processor:  processor,
		})
	}
	if len(units) <= 0 {
		return nil, errors.New("can not found any executedUnit")
	}
	return units, nil
}

func getUsedResourceProcessor(tabulation []config_define.ConfigSourceType) (map[config_define.ConfigSourceType]processor.Processor, error) {
	processorMap := make(map[config_define.ConfigSourceType]processor.Processor)
	manager, err := config_processor_management.GetParseProcessorManager()
	if err != nil {
		return nil, err
	}
	for _, sourceType := range tabulation {
		processor, err := manager.GetAppropriateProcessor(sourceType)
		if err != nil {
			return nil, err
		}
		processorMap[sourceType] = processor
	}
	if len(processorMap) <= 0 {
		return nil, errors.New("can not found any resource processor")
	}
	return processorMap, nil
}

func implementationPhase(ctx *config_context.ConfigInitializationContext, executedUnits []*executedUnit) error {
	for _, unitPtr := range executedUnits {
		unit := unitPtr
		if err := createCurrentReferenceProxy(ctx, unit.processor); err != nil {
			return err
		}
		if err := unit.processor.Process(ctx, unit.loaderData); err != nil {
			return fmt.Errorf("processor %s process error: %s", unit.processor, err)
		}
		if err := unloadCurrentReferenceProxy(ctx); err != nil {
			return err
		}
	}
	return nil
}

func createCurrentReferenceProxy(ctx *config_context.ConfigInitializationContext, processor processor.Processor) error {
	processorTargetProxy, err := proxy.TargetProxy(processor)
	if err != nil {
		return err
	}
	targetHolderNode := node.GenerateCurrentReferenceHolderNode(processorTargetProxy)
	return ctx.AppendCurrentReferenceProxyStore(targetHolderNode)
}

func unloadCurrentReferenceProxy(ctx *config_context.ConfigInitializationContext) error {
	recentlyStoreNode, err := ctx.GetRecentlyCurrentReferenceProxyStore()
	switch err.(type) {
	case *custom_exp.NoResourceError:
		ctx.ResetCurrentReferenceProxyStore()
		return nil
	}
	if err != nil {
		return err
	}
	store := ctx.GetCurrentReferenceProxyStore()
	if recentlyStoreNode == store {
		ctx.ResetCurrentReferenceProxyStore()
		return nil
	}
	return recentlyStoreNode.DetachNode()
}
